/*
 * Copyright 2013 JROSE
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.jrose.core.jdbc.persistence.executor;

import lombok.Setter;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.Validate;
import org.jrose.core.jdbc.DatabaseConnection;
import org.jrose.core.jdbc.entity.EntityDefinition;
import org.jrose.core.jdbc.persistence.annotation.RequireInsert;
import org.jrose.core.jdbc.persistence.annotation.RequireQuery;
import org.jrose.core.jdbc.persistence.annotation.RequireUpdate;
import org.jrose.core.jdbc.persistence.annotation.SqlTemplate;
import org.jrose.core.jdbc.persistence.query.Insert;
import org.jrose.core.jdbc.persistence.query.Query;
import org.jrose.core.jdbc.persistence.query.Sql;
import org.jrose.core.jdbc.persistence.query.Update;
import org.jrose.core.util.AnnotationUtils;
import org.jrose.core.util.ExtendedMap;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.Assert;

import java.io.Serializable;

abstract public class AbstractSqlExecutor<E extends Serializable> implements SqlExecutor<E> {
    protected final Logger log = LoggerFactory.getLogger(getClass());

    protected final DatabaseConnection databaseConnection;
    protected final EntityDefinition<E> entityDefinition;
    protected final SqlTemplate sqlTemplate;

    @Setter protected Insert<E> insert;
    @Setter protected Update<E> update;
    @Setter protected Query<E> query;

    protected AbstractSqlExecutor(DatabaseConnection databaseConnection, EntityDefinition<E> entityDefinition) {
        Validate.notNull(databaseConnection, "DatabaseConnection must not be null");
        Validate.notNull(entityDefinition, "EntityDefinition must not be null");

        this.databaseConnection = databaseConnection;
        this.entityDefinition = entityDefinition;
        this.sqlTemplate = AnnotationUtils.getAnnotation(getClass(), SqlTemplate.class);
        Validate.notNull(this.sqlTemplate, "No @SqlTemplate definied");
        Validate.notBlank(this.sqlTemplate.value(), "No template SQL definied in @SqlTemplate");
    }

    public AbstractSqlExecutor<E> with(Insert<E> insert) {
        Validate.notNull(insert, "Insert must not be null");
        this.insert = insert;
        return this;
    }

    public AbstractSqlExecutor<E> with(Update<E> update) {
        Validate.notNull(update, "Update must not be null");
        this.update = update;
        return this;
    }

    public AbstractSqlExecutor<E> with(Query<E> query) {
        Validate.notNull(query, "Query must not be null");
        this.query = query;
        return this;
    }

    @Override
    final public ExtendedMap execute() {
        if (AnnotationUtils.isAnnotationPresent(getClass(), RequireInsert.class)) {
            Validate.notNull(this.insert, "Insert must not be null");
        }
        if (AnnotationUtils.isAnnotationPresent(getClass(), RequireUpdate.class)) {
            Validate.notNull(this.update, "Update must not be null");
        }
        RequireQuery requireQuery = AnnotationUtils.getAnnotation(getClass(), RequireQuery.class);
        if (requireQuery != null) {
            Validate.notNull(this.query, "Query must not be null");
            if (!requireQuery.allowEmptyCriteria()) {
                Assert.isTrue(query.hasCriteria(), "No criteria specified in query");
            }
        }

        String templateSQL = sqlTemplate.value();
        if (StringUtils.contains(templateSQL, "${TABLE}")) {
            templateSQL = StringUtils.replace(templateSQL, "${TABLE}", entityDefinition.getTableName());
            log.trace("Replace '${TABLE}' in template with: {}", entityDefinition.getTableName());
        }

        Sql sql = generateNamedParameterSQL(templateSQL);
        log.trace("The candidate SQL to be executed is: " + sql.getCandidateSQL());
        return doExecute(sql);
    }

    abstract protected Sql generateNamedParameterSQL(String templateSQL);

    abstract protected ExtendedMap doExecute(Sql sql);
}
